import { RowNode, TreeNode } from '@farris/ui-treetable';
import { cloneDeep, debounce, defaults, merge } from 'lodash-es';
import { BuilderHTMLElement } from '../entity/builder-element';
import { ComponentSchema } from '../entity/builder-schema';
import { ComponentCustomToolbarConfig, IControlService } from '../entity/control-entity';
import { FarrisDesignBaseElement } from './element';

/**
 * UI控件基类
 */
export class FarrisDesignBaseComponent extends FarrisDesignBaseElement {
    element: HTMLElement;
    /** 组件 JSON schema */
    public component: ComponentSchema = null;
    /** References to dom elements */
    refs = {} as any;

    /** 记录父级组件 */
    parent: any;

    /** 用于触发单个组件的内部刷新 */
    triggerRedraw;

    /** 是否将控件的样式应用到Component层级 */
    applyClassToComponent = true;

    /** 控件所属组件id */
    componentId: string;
    /** 控件所属视图模型 id */
    viewModelId: string;

    /** 控件类型 */
    type: string;

    // 控件所属分类
    category: string;

    /** 控件自定义图标按钮 */
    customToolbarConfigs: ComponentCustomToolbarConfig[];

    showName: string;
    parentPathName: string;

    /** 组件在拖拽时是否需要将所属的Component一起拖拽，用于Form、DataGrid、ListView等控件的拖拽     */
    triggerBelongedComponentToMoveWhenMoved = false;

    /** 是否可以选中 */
    checkable = true;

    /** 控件内部的滚动区域id */
    scrollElementId = null;



    /** 组件默认schema元数据 */
    static schema(sources?: any) {
        return merge(
            {}, sources || {});
    }

    /**  返回组件JSON schema数据 */
    getSchema() {
        return this.component;
    }

    /**  组件默认schema元数据 */
    getDefaultSchema() {
        return FarrisDesignBaseComponent.schema();
    }

    /**
     * 合并组件JSON schema数据，可以用于控件添加新属性的场景。
     * @param component 控件实际JSON schema数据
     */
    mergeSchema(component = {}) {
        // return defaultsDeep(component, this.getDefaultSchema());
        return defaults(component, cloneDeep(this.getDefaultSchema()));
    }
    /**
     * key值指向组件id
     */
    get key() {
        return this.component.id;
    }


    /**
     * Initialize a new component
     * @param component The component JSON you wish to initialize.
     * @param options The options for this component.
     */
    constructor(component: any, options: any) {
        super(Object.assign({}, options || {}));

        // 组件自带的id覆盖掉随机生成的id
        if (component && component.id) {
            this.id = component.id;
        }

        this.component = this.mergeSchema(component || {});

        this.type = this.component.type;

        this.parent = this.options.parent;

        this.triggerRedraw = debounce(this.redraw.bind(this), 100);

        // 这个判断不能少，否则在拖拽调整控件时会在新容器中创建两次component
        if (!this.options.skipInit) {
            this.init();
        }

    }


    init() { }

    rebuild(): Promise<any> {
        this.destroy();

        // 收集组件实例
        this.init();

        // 渲染表单
        return this.redraw();
    }

    redraw(): Promise<any> {
        // Don't bother if we have not built yet.
        if (!this.element || !this.element.parentNode) {
            // Return a non-resolving promise.
            return Promise.resolve();
        }
        this.detach();
        this.emit('redraw');

        // 将拖拽的外部控件DOM结构替换为dragcomponent相关结构，此处不可删
        const parent = this.element.parentNode;
        const index = Array.prototype.indexOf.call(parent.children, this.element);
        this.element.outerHTML = this.sanitize(this.render());
        this.element = parent.children[index] as HTMLElement;

        this.setPositionOfBtnGroup();
        return this.attach(this.element);
    }

    render(children: string = `Unknown component: ${this.component.type}`): string {
        return this.renderTemplate('component', {
            visible: true,
            id: this.id,
            classes: this.getClassName(),
            styles: this.getComponentStyles(),
            children,
            canMove: this.checkCanMoveComponent(),
            canSelectParent: this.checkCanSelectParentComponent(),
            canDelete: this.checkCanDeleteComponent(),
            attributes: this.assembleComponentAttributes()
        });
    }

    /**
     * 渲染模板html
     * @param controlType 控件类型
     * @param data 渲染数据
     */
    renderTemplate(controlType: string, data: any = {}) {
        const mode = ['form'];
        // 记录组件JSON schema
        data.component = this.component;
        // 记录组件实例
        data.self = this;

        data.label = this.labelInfo;
        data.id = data.id || this.id;

        const names = [
            `${controlType}-${this.component.type}-${this.key}`,
            `${controlType}-${this.component.type}`,
            `${controlType}-${this.key}`,
            `${controlType}`,
        ];

        const hookName = `${controlType.charAt(0).toUpperCase() + controlType.substring(1, controlType.length)}`;

        const templateResult = this.interpolate(this.getTemplate(names, mode), data);
        return this.hook(
            `render${hookName}`,
            templateResult,
            data,
            mode
        );

    }

    /**
     * 执行控件模板引擎
     * @param names 控件类型
     * @param modes 渲染模式
     */
    getTemplate(names: string[], modes: string[]) {
        modes = Array.isArray(modes) ? modes : [modes];
        names = Array.isArray(names) ? names : [names];

        if (!modes.includes('form')) {
            modes.push('form');
        }

        let result = null;

        // const frameworkTemplates = Templates.current;
        const frameworkTemplates = this.getAllUiTemplates() || [];
        result = this.checkTemplate(frameworkTemplates, names, modes);
        if (result) {
            return result;
        }
    }
    /**
     * 获取ui中定义的控件类型，一直向上找到顶层webform-builder类
     */
    getAllUiTemplates() {
        if (this.parent) {
            return this.parent.getAllUiTemplates();
        }

        return {};
    }

    /**
     * 获取需要添加到Component层级的属性
     */
    getComponentAttributes(): { [attr: string]: string } {
        return null;
    }

    /**
     * 将组件定义的属性组装为字符串
     */
    private assembleComponentAttributes() {
        const attributesObject = this.getComponentAttributes();
        if (!attributesObject) {
            return '';
        }

        let attrStr = '';
        Object.keys(attributesObject).forEach(attrKey => {
            attrStr += ` ${attrKey}='${attributesObject[attrKey]}'`;
        });
        return attrStr;
    }
    /**
     * 获取控件对应的模板引擎
     * @param templates 当前系统支持的所有模板引擎
     * @param names 控件类型
     * @param modes 渲染模式
     */
    checkTemplate(templates: any, names: string[], modes: string[]) {
        for (const name of names) {

            const templatesByName = templates[name];
            if (templatesByName) {
                const templateByMode = this.checkTemplateMode(templatesByName, modes);
                if (templateByMode) {
                    return templateByMode;
                }
            }
        }
        return null;
    }

    checkTemplateMode(templatesByName: any, modes: string[]) {
        for (const mode of modes) {
            const templateByMode = templatesByName[mode];
            // 此处与formio不一样，经过编译后的方法在default里
            if (templateByMode) {
                return templateByMode.default ? templateByMode.default : templateByMode;
            }
        }
        return null;
    }


    destroy() {
        super.destroy();
        this.detach();
    }

    clear() {
        this.detach();
        this.empty(this.getElement());
    }
    /**
     * Remove all event handlers.
     */
    detach() {
        this.refs = {};
        this.removeEventListeners();

    }

    /**
     * 将生成的html注册到dom中
     * @param element dom节点
     * @param content html字符串
     * @param forceSanitize 是否安全转换
     */
    setContent(element: HTMLElement, content: string, forceSanitize: boolean = false) {
        if (element instanceof HTMLElement) {
            element.innerHTML = this.sanitize(content, forceSanitize);
            return true;
        }
        return false;
    }

    /**
     * 返回安全的html
     * @param dirty 原html结构
     * @param forceSanitize 是否强制转换
     */
    sanitize(dirty: string, forceSanitize?: boolean) {

        // 使用DOMPurify安全显示DOM，防网络攻击。这里暂时不用
        // return dompurify.sanitize(string, sanitizeOptions);

        return dirty;
    }

    /**
     * 记录控件类型和DOM节点的映射
     */
    loadRefs(element, refs) {
        for (const ref in refs) {
            if (refs[ref] === 'single') {
                this.refs[ref] = element.querySelector(`[ref='${ref}']`);
            } else {
                this.refs[ref] = element.querySelectorAll(`[ref='${ref}']`);
            }
        }
    }


    /** 可以用于输入控件的label，暂时保留 */
    get labelInfo() {
        const label: any = {};
        label.className = '';
        return label;
    }

    /**
     * 挂载事件，为组件添加事件处理函数
     * @param element 组件对应的DOM节点
     */
    attach(element: BuilderHTMLElement): Promise<any> {

        this.element = element;
        element.component = this;

        this.addClickEvent(element);
        this.addMouseoverEvent(element)

        // Allow global attach.
        this.hook('attachComponent', element, this);

        this.setComponentBasicInfoMap();
        return Promise.resolve();
    }


    // Allow componets to notify when ready.
    get ready(): Promise<any> {
        return Promise.resolve();
    }

    /**
     *  获取组件外层DOM元素
     */
    getElement(): HTMLElement {
        return this.element;
    }

    removeEventListeners() {
        super.removeEventListeners();
    }

    getClassName() {
        let className = '';
        className += ` farris-component farris-component-${this.component.type} `;

        // 包含操作按钮区域，则增加相对位置样式-----
        if (this.checkCanMoveComponent() || this.checkCanDeleteComponent() || (this.customToolbarConfigs && this.customToolbarConfigs.length)) {
            className += ' position-relative';
        }

        // 某些控件的样式不能设置在component层级，需要设置在内部
        if (this.applyClassToComponent) {
            if (this.component.appearance && this.component.appearance.class) {
                className = this.component.appearance.class + ' ' + className;
            }
        }

        // 用于重新渲染组件时，保持选中样式
        if (this.element && this.element.className && this.element.className.includes('dgComponentSelected')) {
            className += ' dgComponentSelected';
        }
        return className;
    }
    /**
     * 获取组件style样式：组件DOM中定义的样式+组件默认样式
     */
    private getComponentStyles() {
        let customStyles = '';
        if (this.component.appearance && this.component.appearance.style) {
            customStyles = this.component.appearance.style || '';
        }
        if (this.component.size && this.component.size.height) {
            customStyles += ';height:' + this.component.size.height + 'px;';
        }
        if (this.component.size && this.component.size.width) {
            customStyles += ';width:' + this.component.size.width + 'px;';
        }
        const innerStyle = this.getStyles();

        return customStyles ? customStyles + ';' + innerStyle : innerStyle;
    }

    /** 组件默认样式 */
    getStyles() {
        return '';
    }
    /**
     * 增加组件点击事件
     * @param element DOM 元素
     */
    private addClickEvent(element: HTMLElement) {
        this.checkable && this.addEventListener(element, 'click', this.onComponentClicked.bind(this));
    }

    /**
     * 点击事件
     * @param e event
     */
    onComponentClicked(e?: PointerEvent) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }

        if (this.element) {
            const currentSelectedElements = document.getElementsByClassName('dgComponentSelected') as HTMLCollectionOf<BuilderHTMLElement>;

            // 重复点击
            const duplicateClick = currentSelectedElements && currentSelectedElements.length === 1 && currentSelectedElements[0] === this.element;
            if (!duplicateClick) {
                for (const element of Array.from(currentSelectedElements)) {
                    element.classList.remove('dgComponentSelected');
                    if (element.componentInstance && element.componentInstance.afterComponentCancelClicked) {
                        element.componentInstance.afterComponentCancelClicked(e);
                    }
                }
                this.element.classList.add('dgComponentSelected');

                if (this.afterComponentClicked) {
                    this.afterComponentClicked(e);
                }
            }

            this.setPositionOfBtnGroup();

        }

        this.emit('componentClicked', { e, componentInstance: this });
    }

    addMouseoverEvent(element: BuilderHTMLElement) {
        if (!this.checkMouseOverActive()) {
            return
        }
        const editorDiv = document.body.querySelector('.editorDiv')
        this.addEventListener(editorDiv, 'mouseover', (e: any) => {
            if (element.contains(e.toElement)) {
                const currentSelectedElements = document.getElementsByClassName('dgComponentHover') as HTMLCollectionOf<BuilderHTMLElement>;
                for (const element of Array.from(currentSelectedElements)) {
                    element.classList.remove('dgComponentHover');
                }
                element.classList.add('dgComponentHover');
            } else {
                element.classList.remove('dgComponentHover');
            }
        });
    }

    /**
     * 由外部触发组件内部的点击事件，用于在IDE设计器中点击控件树节点，进而触发组件内部某部分元素的点击
     */
    triggerComponentInsideClick(node?: TreeNode) {

    }
    /**
     * 由外部触发组件的点击事件，用于在IDE设计器中点击控件树节点，进而触发组件点击的场景
     */
    triggerComponentClick(e?: PointerEvent) {
        this.onComponentClicked(e);
    }
    /**
     * 组件被点击后执行方法，各组件可以实现此方法
     */
    afterComponentClicked(e?: PointerEvent) {

    }
    /**
     * 组件被取消点击后执行方法，各组件可以实现此方法
     */
    afterComponentCancelClicked(e?: PointerEvent) {

    }

    /**
     * 校验组件是否支持移动
     */
    checkCanMoveComponent(): boolean {
        // 顶层form节点不能移动
        if (!this.key && this.type === 'form') {
            return false;
        }

        return true;
    }

    /**
     * 校验组件是否支持选中父级
     */
    checkCanSelectParentComponent(): boolean {
        return false;
    }

    /**
     * 校验组件是否支持删除
     */
    checkCanDeleteComponent() {
        // 顶层form节点不能删除
        if (!this.key && this.type === 'form') {
            return false;
        }

        return true;
    }

    /**
     * 校验组件是否激活 mouseover 事件
     */
    checkMouseOverActive(): boolean {
        return false;
    }

    /**
     * 控件可以拖拽到的最外层容器，用于限制控件向外层容器拖拽的范围。不写则不限制
     */
    getDragScopeElement(): HTMLElement {
        return;
    }

    /** 组装控件右键菜单的方法 */
    resolveContextMenuConfig(rowNode: RowNode, parentRowNode?: RowNode) {
        return [];

    }

    /**
     * 监听滚动事件，重置操作按钮位置
     * @param element HTMLElement
     */
    bindingScrollEvent(element: HTMLElement) {
        // 一般滚动条是出现在组件层级上的
        this.setPositionOfBtnGroupWhenScroll(element);

        // 有些场景下滚动条出现在drag-container层级
        const dragContainerEle = element.querySelector('.drag-container') as HTMLElement;
        if (dragContainerEle) {
            this.setPositionOfBtnGroupWhenScroll(dragContainerEle);
        }

    }
    /**
     * 滚动滚动条时计算工具栏位置
     * @param element 监听滚动的元素
     */
    private setPositionOfBtnGroupWhenScroll(element: HTMLElement) {
        this.addEventListener(element, 'scroll', (e: Event) => {

            this.recordScrollContainer(element);

            const selectDom = (e.target as any).querySelector('.dgComponentSelected');
            if (selectDom) {
                // 获取选中控件所在的滚动区域
                const scrollParentEle = this.getScrollParentElementWhenScroll(selectDom && selectDom.component, element);

                const toolbar = selectDom.querySelector('.component-btn-group');

                // 判断DOM 是否在可视区域内
                if (this.isElementInViewport(selectDom, scrollParentEle)) {
                    if (toolbar) {
                        toolbar.style.display = '';
                        const toolbarRect = toolbar.getBoundingClientRect();
                        if (!(toolbarRect.top === 0 && toolbarRect.left === 0)) {
                            const divPanel = toolbar.querySelector('div');
                            const divPanelRect = divPanel.getBoundingClientRect();

                            divPanel.style.top = toolbarRect.top + 'px';
                            divPanel.style.left = (toolbarRect.left - divPanelRect.width) + 'px';
                        }

                    }
                } else {
                    if (toolbar) {
                        toolbar.style.display = 'none';
                    }
                }
            }
        });
    }
    /**
     * 滚动滚动条时，计算选中控件所在的直接滚动区域
     */
    private getScrollParentElementWhenScroll(cmp: FarrisDesignBaseComponent, currentScrollContainer: HTMLElement) {
        if (!cmp || !window['scrollContainerList']) {
            return currentScrollContainer;
        }

        const scrollContainerArray = Array.from(window['scrollContainerList']);
        if (!scrollContainerArray.length) {
            return currentScrollContainer;
        }
        // 1、若当前只有一个滚动区域：返回当前滚动容器
        if (scrollContainerArray.length === 1) {
            return currentScrollContainer;

        } else {
            // 2、若当前有多个滚动区域：需要定位到当前点击的控件是直接属于哪个滚动区域
            if (cmp && cmp.element && cmp.scrollElementId) {
                if (cmp.element.getAttribute('id') === cmp.scrollElementId) {
                    return cmp.element;
                } else {
                    return cmp.element.querySelector(`[id=${cmp.scrollElementId}]`);
                }
            }
            if (cmp && cmp.parent) {
                return this.getScrollParentElementWhenScroll(cmp.parent, currentScrollContainer);
            }
        }

    }
    /**
     * 判断DOM 是否在可视区域内
     * @param el 元素
     * @param containerEl 容器
     */
    private isElementInViewport(el, containerEl) {
        const container = containerEl.getBoundingClientRect();
        const box = el.getBoundingClientRect();
        const top = box.top >= container.top;
        const bottom = box.top < container.bottom;
        return (top && bottom);
    }


    /**
     * 点击控件时计算控件工具栏位置
     */
    setPositionOfBtnGroup() {
        const toolbar = this.element.querySelector('.component-btn-group') as HTMLElement;

        if (toolbar) {

            // 判断控件是否在可视区域
            let isInView = true;
            const scrollParentEle = this.getScrollParentElementWhenClick(this);
            if (scrollParentEle) {
                isInView = this.isElementInViewport(this.element, scrollParentEle);
            }
            if (!isInView) {
                toolbar.style.display = 'none';
                return;
            }

            // 计算位置
            toolbar.style.display = '';
            const toolbarRect = toolbar.getBoundingClientRect();

            const divPanel = toolbar.querySelector('div');
            const divPanelRect = divPanel.getBoundingClientRect();

            divPanel.style.top = toolbarRect.top + 'px';
            divPanel.style.left = (toolbarRect.left - divPanelRect.width) + 'px';
        }
    }

    /**
     * 点击控件时，定位控件所在的直接滚动区域。用于判断控件当前是否在可视区域内部，从而判断操作按钮是否显示。
     */
    private getScrollParentElementWhenClick(cmp: FarrisDesignBaseComponent) {
        if (!window['scrollContainerList']) {
            return;
        }
        const scrollContainerArray = Array.from(window['scrollContainerList']);
        if (!scrollContainerArray.length) {
            return;
        }
        // 1、若当前只有一个滚动区域：返回滚动区域
        if (scrollContainerArray.length === 1) {
            const scrollContainerId = scrollContainerArray[0];
            const scrollContainer = document.querySelector(`[id=${scrollContainerId}]`);

            if (scrollContainer && scrollContainer.contains(cmp.element)) {
                return scrollContainer;
            }

        } else {
            // 2、若当前有多个滚动区域：需要定位到当前点击的控件是属于哪个滚动区域
            if (cmp && cmp.element && cmp.scrollElementId) {
                if (cmp.element.getAttribute('id') === cmp.scrollElementId) {
                    return cmp.element;
                } else {
                    return cmp.element.querySelector(`[id=${cmp.scrollElementId}]`);
                }
            }
            if (cmp && cmp.parent) {
                return this.getScrollParentElementWhenClick(cmp.parent);
            }
        }

    }
    /**
     * 记录滚动区域
     * @param element 组件DOM节点
     */
    private recordScrollContainer(element: HTMLElement) {
        if (!window['scrollContainerList']) { window['scrollContainerList'] = new Set(); }

        const id = element.getAttribute('id');
        if (!id) {
            return;
        }

        window['scrollContainerList'].add(id);

        this.scrollElementId = id;
    }

    /**
     * 设置控件id，控件基本信息（展示名称、路径）的映射关系。各控件可重写此方法。
     */
    setComponentBasicInfoMap() {
        if (!this.component.id) {
            return;
        }
        // 获取控件展示中文名称
        const showName = this.getControlShowName();
        const domService = this.options.designerHost.getService('DomService');
        if (!domService) {
            return;
        }
        this.showName = showName;

        // 控件从根节点开始的中文路径
        this.parentPathName = this.showName;

        // 将路径信息保存到DomService，方便控件树和事件编辑获取
        domService.controlBasicInfoMap.set(this.component.id, {
            showName,
            parentPathName: this.parentPathName
        });

    }
    /**
     * 获取控件展示中文名称。包含title等属性的控件取title，不包含的取控件类型的中文名称。
     */
    getControlShowName() {
        const title = this.component.text || this.component.name || this.component.title || this.component.caption || this.component.mainTitle;
        if (title) {
            return title;
        }

        if (this.options.designerHost) {
            const controlCreatorService = this.options.designerHost.getService('ControlCreatorService');
            const controlService = controlCreatorService.controlService as IControlService;
            const DgControl = controlService.getDgControl();


            const componentTypeName = DgControl[this.component.type] && DgControl[this.component.type].name;
            return componentTypeName || this.component.id;
        }
    }
    /**
     * 获取组件在表单DOM中所属的Component的实例
     * @param cmpInstance 组件实例
     */
    getBelongedComponentInstance(componentInstance: any) {
        if (!componentInstance) {
            return;
        }
        if (componentInstance.type === 'Component') {
            return componentInstance;
        }
        const grandParent = this.getBelongedComponentInstance(componentInstance.parent);
        if (grandParent) {
            return grandParent;
        }

    }
    /**
     * 计算页面中选中控件的操作按钮位置。
     * 场景：控件内部点击收折或者切换显示内容后，需要重新计算页面中下方选中控件的按钮位置
     */
    setPositionOfSelectedComponentBtnGroup() {
        const selectedEle = document.querySelector('.dgComponentSelected') as HTMLElement;
        if (!selectedEle) {
            return;
        }
        const selectedEleRect = selectedEle.getBoundingClientRect();
        const elementRect = this.element.getBoundingClientRect();

        const toolbar = selectedEle.querySelector('.component-btn-group') as HTMLElement;
        if (toolbar) {
            toolbar.style.display = '';
            const toolbarRect = toolbar.getBoundingClientRect();
            const isBelow = elementRect.top < selectedEleRect.top;

            // 选中控件已显示并且在基准位置的下方
            if (toolbarRect.top !== 0 && isBelow) {
                const divPanel = toolbar.querySelector('div');
                const divPanelRect = divPanel.getBoundingClientRect();

                divPanel.style.top = toolbarRect.top + 'px';
                divPanel.style.left = (toolbarRect.left - divPanelRect.width) + 'px';
            }

        }

    }
}

